# @HEADER
# ************************************************************************
#
#            TriBITS: Tribal Build, Integrate, and Test System
#                    Copyright 2013 Sandia Corporation
#
# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
# the U.S. Government retains certain rights in this software.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the Corporation nor the names of the
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ************************************************************************
# @HEADER


include_guard()

include(TribitsGeneralMacros)
include(TribitsPackageDependencies)
include(TribitsWritePackageConfigFileHelpers)

include(MessageWrapper)


################################################################################
#
# User-level functions
#
################################################################################


# @FUNCTION: tribits_extpkg_write_config_file()
#
# Write out a ``<tplName>Config.cmake`` file for a TriBITS TPL given the list
# of include directories and libraries for an external package/TPL.
#
# Usage::
#
#   tribits_write_external_package_config_file(
#     <tplName> <tplConfigFile> )
#
# The arguments are:
#
#   ``<tplName>``: Name of the external package/TPL
#
#   ``<tplConfigFile>``: Full file path for the ``<tplName>Config.cmake``
#   file that will be written out.
#
# This function just calls `tribits_extpkg_write_config_file_str()`_
# and writes that text to the file ``<tplConfigFile>`` so see that function
# for more details.
#
# NOTE: This is used for a classic TriBITS TPL that does **not** use
# ``find_package(<externalPkg>)`` with modern IMPORTED targets.
#
function(tribits_extpkg_write_config_file  tplName  tplConfigFile)
  tribits_extpkg_write_config_file_str(${tplName} tplConfigFileStr)
  file(WRITE "${tplConfigFile}" "${tplConfigFileStr}")
endfunction()


# @FUNCTION: tribits_extpkg_write_config_version_file()
#
# Write out a ``<tplName>ConfigVersion.cmake`` file.
#
# Usage::
#
#   tribits_write_external_package_config_version_file(
#     <tplName> <tplConfigVersionFile> )
#
# ToDo: Add version arguments!
#
# The arguments are:
#
#   ``<tplName>``: Name of the external package/TPL
#
#   ``<tplConfigVersionFile>``: Full file path for the
#   ``<tplName>ConfigVersion.cmake`` file that will be written out.
#
function(tribits_extpkg_write_config_version_file  tplName  tplConfigVersionFile)
  set(tplConfigVersionFileStr "")
  string(APPEND tplConfigVersionFileStr
    "# Package config file for external package/TPL '${tplName}'\n"
    "#\n"
    "# Generated by CMake, do not edit!\n"
    "\n"
    "if (TRIBITS_FINDING_RAW_${tplName}_PACKAGE_FIRST)\n"
    "  set(PACKAGE_VERSION_COMPATIBLE FALSE)\n"
    "  set(PACKAGE_VERSION_UNSUITABLE TRUE)\n"
    "else()\n"
    "  set(PACKAGE_VERSION_COMPATIBLE TRUE)\n"
    "endif()\n"
    "\n"
    "# Currently there is no version information\n"
    "set(PACKAGE_VERSION UNKNOWN)\n"
    "set(PACKAGE_VERSION_EXACT FALSE)\n"
    )
  file(WRITE "${tplConfigVersionFile}" "${tplConfigVersionFileStr}")
endfunction()


# @FUNCTION: tribits_extpkg_install_config_file()
#
# Install an already-generated ``<tplName>Config.cmake`` file.
#
# Usage::
#
#   tribits_write_external_package_install_config_file(
#     <tplName> <tplConfigFile> )
#
# The arguments are:
#
#   ``<tplName>``: Name of the external package/TPL
#
#   ``<tplConfigFile>``: Full file path for the ``<tplName>Config.cmake``
#   file that will be installed into the correct location.
#
function(tribits_extpkg_install_config_file  tplName  tplConfigFile)
 install(
    FILES "${tplConfigFile}"
    DESTINATION "${${PROJECT_NAME}_INSTALL_LIB_DIR}/external_packages/${tplName}"
    )
endfunction()


# @FUNCTION: tribits_extpkg_install_config_version_file()
#
# Install an already-generated ``<tplName>ConfigVersion.cmake`` file.
#
# Usage::
#
#   tribits_write_external_package_install_config_version_file(
#     <tplName> <tplConfigVersionFile> )
#
# The arguments are:
#
#   ``<tplName>``: Name of the external package/TPL
#
#   ``<tplConfigVersionFile>``: Full file path for the
#   ``<tplName>ConfigVersion.cmake`` file that will be installed into the
#   correct location ``${${PROJECT_NAME}_INSTALL_LIB_DIR}/external_packages/``
#
function(tribits_extpkg_install_config_version_file  tplName
     tplConfigVersionFile
  )
 install(
    FILES "${tplConfigVersionFile}"
    DESTINATION "${${PROJECT_NAME}_INSTALL_LIB_DIR}/external_packages/${tplName}"
    )
endfunction()


################################################################################
#
# TriBITS Implementation functions
#
################################################################################


# @FUNCTION: tribits_extpkgwit_create_package_config_file()
#
# Create the ``<tplName>Config.cmake`` file for a TriBITS external package/TPL
# that is defined by a set of IMPORTED targets by call to
# ``find_package(<externalPkg>)``
#
# Usage::
#
#   tribits_extpkgwit_create_package_config_file( <tplName>
#     INNER_FIND_PACKAGE_NAME <externalPkg>
#     IMPORTED_TARGETS_FOR_ALL_LIBS <importedTarget0> <importedTarget1> ... )
#
function(tribits_extpkgwit_create_package_config_file  tplName)
  # Parse arguments
  cmake_parse_arguments(
     PARSE_ARGV 1
     PARSE "" "" # prefix, options, one_value_keywords
     "INNER_FIND_PACKAGE_NAME;IMPORTED_TARGETS_FOR_ALL_LIBS"  #multi_value_keywords
     )
  tribits_check_for_unparsed_arguments(PARSE)
  tribits_assert_parse_arg_one_value(PARSE  INNER_FIND_PACKAGE_NAME)
  tribits_assert_parse_arg_one_or_more_values(PARSE  IMPORTED_TARGETS_FOR_ALL_LIBS)
  set(externalPkg ${PARSE_INNER_FIND_PACKAGE_NAME})

  # Create header for <tplName>Config.cmake file
  set(configFileStr "")
  tribits_extpkgwit_append_package_config_file_header_str(
    ${tplName}  ${externalPkg}  configFileStr)

  # Get ${externalPkg} from where you found it before (see note below)
  tribits_extpkgwit_append_find_dependency_external_package_str(
    ${tplName}  ${externalPkg}  configFileStr)

  # Pull in upstream <UpstreamPkg>Config.cmake files
  tribits_extpkg_append_find_upstream_dependencies_str(${tplName} configFileStr)

  # Add the ${tplName}::all_libs target and link to this ${externalPkg}
  # package's native IMPORTED targets
  tribits_extpkg_append_create_all_libs_target_str( ${tplName}
    LIB_TARGETS_LIST  ${PARSE_IMPORTED_TARGETS_FOR_ALL_LIBS}
    CONFIG_FILE_STR_INOUT  configFileStr )

  # Also link against upstream package's `<UpstreamPkg>::all_libs` targets
  tribits_extpkg_append_target_link_libraries_to_upstream_all_libs_targets_str(${tplName}
    ${tplName}::all_libs  configFileStr)

  tribits_extpkg_append_tribits_compliant_package_config_vars_str(${tplName}
    configFileStr)

  tribits_extpkg_write_package_config_file_from_str(${tplName}  "${configFileStr}")
endfunction()


function(tribits_extpkgwit_append_find_dependency_external_package_str
    tplName  externalPkg  configFileStrVarInOut
  )
  set(configFileStr "${${configFileStrVarInOut}}")
  if (${externalPkg}_DIR)
    string(APPEND configFileStr
      "set(${externalPkg}_DIR \"${${externalPkg}_DIR}\")\n" )
  endif()
  string(APPEND configFileStr
    "find_dependency(${externalPkg})\n\n")
  set(${configFileStrVarInOut} "${configFileStr}" PARENT_SCOPE)
endfunction()
#
# NOTE: Above, ${externalPkg}_DIR is only set when
# find_package(${externalPkg}) finds a package configure file
# ${externalPkg}Config.cmake and **not** when it uses a
# Find${externalPkg}.cmake module.  Therefore, there is no reason to set
# ${externalPkg}_DIR in this file if it will not be used.


function(tribits_extpkgwit_append_package_config_file_header_str
    tplName  externalPkg  configFileStrVarInOut
  )
  set(configFileStr "${${configFileStrVarInOut}}")
  string(APPEND configFileStr
    "# TriBITS-compliant Package config file for external package/TPL '${tplName}'\n"
    "# based on non TriBITS-compliant external package '${externalPkg}' that uses\n"
    "# modern IMPORTED targets\n"
    "#\n"
    "# Generated by CMake, do not edit!\n"
    "\n"
    "# Guard against multiple inclusion\n"
    "if (TARGET ${tplName}::all_libs)\n"
    "  return()\n"
    "endif()\n"
    "\n"
    "include(CMakeFindDependencyMacro)\n\n"
    )
  set(${configFileStrVarInOut} "${configFileStr}" PARENT_SCOPE)
endfunction()


# @FUNCTION: tribits_extpkg_write_config_file_str()
#
# Create the text string for a ``<tplName>Config.cmake`` file given the list
# of include directories and libraries for an external package/TPL from the
# legacy TriBITS TPL specification.
#
# Usage::
#
#   tribits_extpkg_write_config_file_str(
#     <tplName> <tplConfigFileStrOut> )
#
# The function arguments are:
#
#   ``<tplName>``: Name of the external package/TPL
#
#   ``<tplConfigFileStrOut>``: Name of variable that will contain the string
#   for the config file on output.
#
# This function reads from the (cache) variables
#
#   * ``TPL_<tplName>_INCLUDE_DIRS``
#   * ``TPL_<tplName>_LIBRARIES``
#   * ``<tplName>_LIB_ENABLED_DEPENDENCIES``
#
# (which must already be set) and uses that information to produce the
# contents of the ``<tplName>Config.cmake`` which is returned as a string
# variable that contains IMPORTED targets to represent these libraries and
# include directories as well as ``find_dependency()`` calls for upstream
# packages listed in ``<tplName>_LIB_ENABLED_DEPENDENCIES``.
#
# The arguments in ``TPL_<tplName>_LIBRARIES`` are handled in special ways in
# order to create the namespaced IMPORTED targets
# ``tribits::<tplName>::<libname>`` and the ``<tplName>::all_libs`` target
# that depends on these.
#
# The types of arguments that are handled and how the are interpreted:
#
#   ``<abs-base-path>/[lib]<libname>.<longest-ext>``
#
#     Arguments that are absolute file paths are treated as libraries and an
#     imported target name ``<libname>`` is derived from the file name (of the
#     form ``lib<libname>.<longest-ext>`` removing beginning ``lib`` and file
#     extension ``.<longest-ext>``).  The IMPORTED target
#     ``tribits::<tplName>::<libname>`` is created and the file path is set
#     using the ``IMPORTED_LOCATION`` target property.
#
#   ``-l<libname>``
#
#     Arguments of the form ``-l<libname>`` are used to create IMPORTED
#     targets with the name ``tribits::<tplName>::<libname>`` using the
#     ``IMPORTED_LIBNAME`` target property.
#
#   ``<libname>``
#
#     Arguments that are a raw name that matches the regex
#     ``^[a-zA-Z_][a-zA-Z0-9_-]*$`` are interpreted to be a library name
#     ``<libname>`` and is used to create an IMPORTED targets
#     ``<tplName>::<libname>`` using the ``IMPORTED_LIBNAME`` target property.
#
#   ``-L<dir>``
#
#     Link directories.  These are pulled off and added to the
#     ``<tplName>::all_libs`` using ``target_link_options()``.  (The order of
#     these options is maintained.)
#
#   ``-<any-option>``
#
#     Any other option that starts with ``-`` is assumed to
#     be a link argument where the order does not matter in relation to the
#     libraries (but the order of these extra options are maintained w.r.t. each
#     other).
#
#   ``<unrecognized>``
#
#     Any other argument that does not match one of the above patterns is
#     regarded as an error.
#
# For more details on the handling of individual ``TPL_<tplName>_LIBRARIES``
# arguments, see `tribits_extpkg_tpl_libraries_entry_type()`_.
#
# The list of directories given in ``TPL_<tplName>_INCLUDE_DIRS`` is added to
# the ``<tplName>::all_libs`` target using ``target_include_directories()``.
#
# Finally, for every ``<upstreamTplName>`` listed in
# ``<tplName>_LIB_ENABLED_DEPENDENCIES``, a link dependency is created using
# ``target_link_library(<tplName>::all_libs INTERFACE <upstreamTplName>)``.
#
# NOTE: The IMPORTED targets generated for each library argument
# ``<tplName>::<libname>`` are prefixed with ``tribits::`` to give
# ``tribits::<tplName>::<libname>``.  This is to avoid clashing with IMPORTED
# targets ``<tplName>::<libname>`` from other package config files
# ``<tplName>Config.cmake`` or find modules ``Find<tplName>.cmake`` that may
# clash (see TriBITSPub/TriBITS#548). But the generated INTERFACE IMPORTED
# target ``<tplName>::all_libs`` is **not** namespaced with ``tribits::``
# since the ``all_libs`` target is unlikely to clash.  The targets
# ``tribits::<tplName>::<libname>`` are not directly used in downstream
# ``target_link_library()`` calls so the names of these targets are really
# just an implementation detail.  (The reason we give these a name based of
# the library name they represent ``<libname>`` is to make it more clear what
# the matching library is and to make the name unique.)
#
function(tribits_extpkg_write_config_file_str  tplName  tplConfigFileStrOut)

  # A) Set up beginning of config file text
  set(configFileStr "")
  string(APPEND configFileStr
    "# Package config file for external package/TPL '${tplName}'\n"
    "#\n"
    "# Generated by CMake, do not edit!\n"
    "\n"
    "# Guard against multiple inclusion\n"
    "if (TARGET ${tplName}::all_libs)\n"
    "  return()\n"
    "endif()\n"
    "\n"
    )

  # B) Pull in upstream packages
  tribits_extpkg_append_find_upstream_dependencies_str(${tplName}
    configFileStr)

  # C) Create IMPORTED library targets from TPL_${tplName}_LIBRARIES
  tribits_extpkg_process_libraries_list(
    ${tplName}
    LIB_TARGETS_LIST_OUT  libTargets
    LIB_LINK_FLAGS_LIST_OUT  libLinkFlags
    CONFIG_FILE_STR_INOUT  configFileStr
    )

  # D) Create the <tplName>::all_libs target
  tribits_extpkg_append_create_all_libs_target_str(
    ${tplName}
    LIB_TARGETS_LIST  ${libTargets}
    LIB_LINK_FLAGS_LIST  ${libLinkFlags}
    CONFIG_FILE_STR_INOUT  configFileStr
    )

  # E) Add standard TriBITS-compliant external package vars
  tribits_extpkg_append_tribits_compliant_package_config_vars_str(${tplName}  configFileStr)

  # F) Set the output
  set(${tplConfigFileStrOut} "${configFileStr}" PARENT_SCOPE)

endfunction()


# @FUNCTION: tribits_extpkg_append_find_upstream_dependencies_str()
#
# Add includes for all upstream external packages/TPLs listed in
# ``<tplName>_LIB_ENABLED_DEPENDENCIES``.
#
# Usage::
#
#   tribits_extpkg_append_find_upstream_dependencies_str(tplName
#     configFileFragStrInOut)
#
# NOTE: This also requires that
# ``<upstreamTplName>_TRIBITS_COMPLIANT_PACKAGE_CONFIG_FILE`` be set for each
# external package/TPL listed in ``<tplName>_LIB_ENABLED_DEPENDENCIES``.
#
function(tribits_extpkg_append_find_upstream_dependencies_str
    tplName  configFileFragStrInOut
  )
  if (NOT "${${tplName}_LIB_ENABLED_DEPENDENCIES}" STREQUAL "")
    set(configFileFragStr "${${configFileFragStrInOut}}")
    foreach (upstreamTplDepEntry IN LISTS ${tplName}_LIB_ENABLED_DEPENDENCIES)
      tribits_extpkg_get_dep_name_and_vis(
        "${upstreamTplDepEntry}"  upstreamTplDepName  upstreamTplDepVis)
      if (NOT "${${upstreamTplDepName}_TRIBITS_COMPLIANT_PACKAGE_CONFIG_FILE_DIR}"
          STREQUAL ""
        )
        set(upstreamTplPackageConfigFileDir
          "${${upstreamTplDepName}_TRIBITS_COMPLIANT_PACKAGE_CONFIG_FILE_DIR}")
      else()
        set(upstreamTplPackageConfigFileDir
          "\${CMAKE_CURRENT_LIST_DIR}/../${upstreamTplDepName}")
      endif()
      string(APPEND configFileFragStr
        "if (NOT TARGET ${upstreamTplDepName}::all_libs)\n"
        "  include(\"${upstreamTplPackageConfigFileDir}/${upstreamTplDepName}Config.cmake\")\n"
        "endif()\n"
        "\n"
        )
    endforeach()
    set(${configFileFragStrInOut} "${configFileFragStr}" PARENT_SCOPE)
  endif()
endfunction()
#
# NOTE: Above, we include the upstream ${upstreamTplDepName}Config.cmake file
# from either beside the current location (in the build tree or the install
# tree) or we use the location set in
# ${upstreamTplDepName}_TRIBITS_COMPLIANT_PACKAGE_CONFIG_FILE_DIR which would
# have been set by the ${upstreamTplDepName}Config.cmake file itself from an
# upstream install as pulled in from a TriBITS-compliant external package.


# @FUNCTION: tribits_extpkg_process_libraries_list()
#
# Read the ``TPL_<tplName>_LIBRARIES`` and
# ``<tplName>_LIB_ENABLED_DEPENDENCIES`` list variables and produce the string
# for the IMPORTED targets commands with upstream linkages and return list of
# targets and left over linker flags.
#
# Usage::
#
#   tribits_extpkg_process_libraries_list(
#     <tplName>
#     LIB_TARGETS_LIST_OUT <libTargetsListOut>
#     LIB_LINK_FLAGS_LIST_OUT <libLinkFlagsListOut>
#     CONFIG_FILE_STR_INOUT <configFileFragStrInOut>
#     )
#
# The arguments are:
#
#   ``<tplName>``: [In] Name of the external package/TPL
#
#   ``<libTargetsListOut>``: [Out] Name of list variable that will be set with
#   the list of IMPORTED library targets generated from this list.
#
#   ``<libLinkFlagsListOut>``: [Out] Name of list variable that will be set
#   with the list of ``-L<dir>`` library directory paths.
#
#   ``<configFileFragStrInOut>``: [Inout] A string variable that will be
#   appended with the IMPORTED library commands for the list of targets given
#   in ``<libTargetsList>``.
#
function(tribits_extpkg_process_libraries_list  tplName)

  # A) Parse commandline arguments

  cmake_parse_arguments(
     PARSE #prefix
     ""    #options
     "LIB_TARGETS_LIST_OUT;LIB_LINK_FLAGS_LIST_OUT;CONFIG_FILE_STR_INOUT"  #one_value_keywords
     ""    #multi_value_keywords
     ${ARGN}
     )
  tribits_check_for_unparsed_arguments()

  # Capture the initial input string in case the name of the var
  # 'configFileStr' is the same in the parent scope.
  set(configFileStrInit "${${PARSE_CONFIG_FILE_STR_INOUT}}")

  # B) Create IMPORTED library targets from TPL_${tplName}_LIBRARIES

  set(configFileStr "")
  set(libTargets "")
  set(previousLibProcessed "")

  # Iterate through libs in reverse order setting dependencies on the libs
  # that came before them so CMake will put in right order on the link line.

  set(libLinkFlagsList "")  # Will be filled in reverse order below

  set(reverseLibraries ${TPL_${tplName}_LIBRARIES})
  list(REVERSE reverseLibraries)

  foreach (libentry IN LISTS reverseLibraries)
    tribits_extpkg_tpl_libraries_entry_type(${libentry} libEntryType)
    if (libEntryType STREQUAL "UNSUPPORTED_LIB_ENTRY")
      message_wrapper(SEND_ERROR
        "ERROR: Can't handle argument '${libentry}' in list TPL_${tplName}_LIBRARIES")
    elseif (libEntryType STREQUAL "LIB_DIR_LINK_OPTION")
      list(APPEND libLinkFlagsList "${libentry}")
    elseif (libEntryType STREQUAL "GENERAL_LINK_OPTION")
      message_wrapper("-- NOTE: Moving the general link argument '${libentry}' in TPL_${tplName}_LIBRARIES forward on the link line which may change the link and break the link!")
      list(APPEND libLinkFlagsList "${libentry}")
    else()
      tribits_extpkg_process_libraries_list_library_entry(
        ${tplName}  "${libentry}"  ${libEntryType}  libTargets  previousLibProcessed
        configFileStr )
    endif()
  endforeach()

  list(REVERSE libLinkFlagsList) # Put back in original order

  # C) Set output arguments:
  set(${PARSE_LIB_TARGETS_LIST_OUT} "${libTargets}" PARENT_SCOPE)
  set(${PARSE_LIB_LINK_FLAGS_LIST_OUT} "${libLinkFlagsList}" PARENT_SCOPE)
  set(${PARSE_CONFIG_FILE_STR_INOUT} "${configFileStrInit}${configFileStr}"
    PARENT_SCOPE)

endfunction()


# @FUNCTION: tribits_extpkg_tpl_libraries_entry_type()
#
# Returns the type of the library entry in the list TPL_<tplName>_LIBRARIES
#
# Usage::
#
#   tribits_extpkg_tpl_libraries_entry_type(<libentry>  <libEntryTypeOut>)
#
# Arguments:
#
#   ``<libentry>`` [in]: Element of ``TPL_<tplName>_LIBRARIES``
#
#   ``<libEntryTypeOut>`` [out]: Variable set on output to the type of entry.
#
# The types of entries set on ``libEntryTypeOut`` include:
#
#   * ``FULL_LIB_PATH``: A full library path
#
#   * ``LIB_NAME_LINK_OPTION``: A library name link option of the form
#     ``-l<libname>``
#
#   * ``LIB_NAME``: A library name of the form ``<libname>``
#
#   * ``LIB_DIR_LINK_OPTION``: A library directory search option of the form
#     ``-L<dir>``
#
#   * ``GENERAL_LINK_OPTION``: Some other general link option that starts with
#     ``-`` but is not ``-l`` or ``-L``.
#
#   * ``UNSUPPORTED_LIB_ENTRY``: An unsupported lib option
#
function(tribits_extpkg_tpl_libraries_entry_type  libentry  libEntryTypeOut)
  string(SUBSTRING "${libentry}" 0 1 firstCharLibEntry)
  string(SUBSTRING "${libentry}" 0 2 firstTwoCharsLibEntry)
  if (firstTwoCharsLibEntry STREQUAL "-l")
    set(libEntryType LIB_NAME_LINK_OPTION)
  elseif (firstTwoCharsLibEntry STREQUAL "-L")
    set(libEntryType LIB_DIR_LINK_OPTION)
  elseif (firstCharLibEntry STREQUAL "-")
    set(libEntryType GENERAL_LINK_OPTION)
  elseif (IS_ABSOLUTE "${libentry}")
    set(libEntryType FULL_LIB_PATH)
  elseif (libentry MATCHES "^[a-zA-Z_][a-zA-Z0-9_-]*$")
    set(libEntryType LIB_NAME)
  else()
    set(libEntryType UNSUPPORTED_LIB_ENTRY)
  endif()
  set(${libEntryTypeOut} ${libEntryType} PARENT_SCOPE)
endfunction()
# NOTE: Above, if libentry is only 1 char long, then firstTwoCharsLibEntry is
# also 1 char long and the above logic still works.


# Function to process a library inside of loop over
# ``TPL_<tplName>_LIBRARIES`` in the function
# tribits_extpkg_process_libraries_list().
#
# This also puts in linkages to upstream TPLs ``<tplName>::all_libs`` listed
# in ``<tplName>_LIB_ENABLED_DEPENDENCIES``.
#
function(tribits_extpkg_process_libraries_list_library_entry
    tplName  libentry  libEntryType
    libTargetsInOut  previousLibProcessedInOut  configFileStrInOut
  )
  # Set local vars for inout vars
  set(libTargets ${${libTargetsInOut}})
  set(previousLibProcessed ${${previousLibProcessedInOut}})
  set(configFileStr ${${configFileStrInOut}})
  # Get libname
  tribits_extpkg_get_libname_and_path_from_libentry(
    "${libentry}"  ${libEntryType}  libname  libpath)
  # Create IMPORTED library target
  set(prefixed_libname "tribits::${tplName}::${libname}")
  if (NOT (prefixed_libname IN_LIST libTargets))
    tribits_extpkg_append_add_library_str (${libname} ${prefixed_libname}
      ${libEntryType} "${libpath}" configFileStr)
    if (previousLibProcessed)
      # This is not the first lib so we only need to link to the previous lib
      string(APPEND configFileStr
        "target_link_libraries(${prefixed_libname}\n"
        "  INTERFACE tribits::${tplName}::${previousLibProcessed})\n"
        )
    else()
      # Only on the first lib do we add dependencies on all of the
      # `<UpstreamPkg>::all_libs` targets
      tribits_extpkg_append_target_link_libraries_to_upstream_all_libs_targets_str( ${tplName}
        ${prefixed_libname}  configFileStr )
    endif()
    string(APPEND configFileStr
      "\n")
    # Update for next loop
    set(previousLibProcessed ${libname})
    list(APPEND libTargets ${prefixed_libname})
  endif()
  # Set output vars
  set(${libTargetsInOut} ${libTargets} PARENT_SCOPE)
  set(${previousLibProcessedInOut} ${previousLibProcessed} PARENT_SCOPE)
  set(${configFileStrInOut} ${configFileStr} PARENT_SCOPE)
endfunction()
# NOTE: Above, we only need to link the first library
# tribits::<tplName>::<libname0> against the upstream TPL libraries
# <upstreamTpl>::all_libs.  The other imported targets
# tribits::<tplName>::<libnamei> for this TPL are linked to this first
# tribits::<tplName>::<libname0> which has the needed dependencies.


function(tribits_extpkg_get_libname_and_path_from_libentry
    libentry  libEntryType  libnameOut  libpathOut
  )
  if (libEntryType STREQUAL "FULL_LIB_PATH")
    tribits_extpkg_get_libname_from_full_lib_path("${libentry}" libname)
    set(libpath "${libentry}")
  elseif (libEntryType STREQUAL "LIB_NAME_LINK_OPTION")
    tribits_extpkg_get_libname_from_lib_name_link_option("${libentry}" libname)
    set(libpath "")
  elseif (libEntryType STREQUAL "LIB_NAME")
    set(libname "${libentry}")
    set(libpath "")
  else()
    message(FATAL_ERROR "Error libEntryType='${libEntryType}' not supported here!")
  endif()
  set(${libnameOut} ${libname} PARENT_SCOPE)
  set(${libpathOut} ${libpath} PARENT_SCOPE)
endfunction()


function(tribits_extpkg_append_add_library_str
    libname  prefix_libname  libEntryType  libpath
    configFileStrInOut
  )
  set(configFileStr "${${configFileStrInOut}}")
  if (libEntryType STREQUAL "FULL_LIB_PATH")
    get_filename_component(libExt "${libpath}" LAST_EXT)
    if (libExt  STREQUAL ".a")
      set(libType  STATIC)
    else()
      set(libType  UNKNOWN)
    endif()
    string(APPEND configFileStr
      "add_library(${prefixed_libname} IMPORTED ${libType})\n"
      "set_target_properties(${prefixed_libname} PROPERTIES\n"
      "  IMPORTED_LOCATION \"${libpath}\")\n"
      )
  elseif (
      (libEntryType STREQUAL "LIB_NAME_LINK_OPTION")
      OR (libEntryType STREQUAL "LIB_NAME")
    )
    string(APPEND configFileStr
      "add_library(${prefixed_libname} IMPORTED INTERFACE)\n"
      "set_target_properties(${prefixed_libname} PROPERTIES\n"
      "  IMPORTED_LIBNAME \"${libname}\")\n"
      )
  else()
    message(FATAL_ERROR "Error libEntryType='${libEntryType}' not supported here!")
  endif()
  set(${configFileStrInOut} "${configFileStr}" PARENT_SCOPE)
endfunction()


function(tribits_extpkg_get_libname_from_full_lib_path  full_lib_path
    libnameOut
  )
  # Should be an absolute library path
  get_filename_component(full_libname "${full_lib_path}" NAME_WE)
  # Begins with 'lib'?
  tribits_extpkg_libname_begins_with_lib("${full_libname}" beginsWithLib)
  # Assert is a valid lib name and get lib name
  set(libname "")
  string(LENGTH "${full_libname}" full_libname_len)
  if (full_libname_len LESS 0)
    tribits_extpkg_print_invalid_lib_name(${tplName} "${full_lib_path}")
  endif()
  if (WIN32)
    # Native windows compilers does not prepend library names with 'lib'
    set(libname "${full_libname}")
  elseif (APPLE)
    # On MacOSX, CMake allows using frameworks that *don't* begin with 'lib'
    # so we have to allow for that
    if (beginsWithLib)
      string(SUBSTRING "${full_libname}" 3 -1 libname)
    else()
      # Must be a framework dir with extension .framework
      get_filename_component(last_ext "${full_lib_path}" LAST_EXT)
      if (last_ext  STREQUAL ".framework")
        set(libname "${full_libname}")
      else()
        tribits_extpkg_print_invalid_lib_name(${tplName} "${full_lib_path}")
      endif()
    endif()
  else() # I.e. Linux
    # Every other system (i.e. Linux) prepends the library name with 'lib' so
    # assert for that
    if (NOT beginsWithLib)
      tribits_extpkg_print_invalid_lib_name(${tplName} "${full_lib_path}")
    else()
      string(SUBSTRING "${full_libname}" 3 -1 libname)
    endif()
  endif()
  # Set output
  set(${libnameOut} ${libname} PARENT_SCOPE)
endfunction()


function(tribits_extpkg_libname_begins_with_lib  full_libname
    libnameBeginsWithLibOut
  )
  string(SUBSTRING "${full_libname}" 0 3 libPart)
  if (libPart STREQUAL "lib")
    set(libnameBeginsWithLib TRUE)
  else()
    set(libnameBeginsWithLib FALSE)
  endif()
  set(${libnameBeginsWithLibOut} ${libnameBeginsWithLib} PARENT_SCOPE)
endfunction()


function(tribits_extpkg_get_libname_from_lib_name_link_option
    lib_name_link_option  libnameOut
  )
  # Assert begging part '-l'
  string(SUBSTRING "${lib_name_link_option}" 0 2 firstTwoCharsLibEntry)
  if ( )
    tribits_extpkg_print_invalid_lib_link_option(${tplName} "${lib_name_link_option}")
  endif()
  # Get <libname> from -l<libname>
  string(SUBSTRING "${lib_name_link_option}" 2 -1 libname)
  # Set output
  set(${libnameOut} ${libname} PARENT_SCOPE)
endfunction()


function(tribits_extpkg_print_invalid_lib_name  tplName  full_libname)
  message_wrapper(SEND_ERROR
    "ERROR: TPL_${tplName}_LIBRARIES entry '${full_libname}' not a valid lib file name!")
endfunction()


function(tribits_extpkg_print_invalid_lib_link_option  tplName  liblinkoption)
  message(SEND_ERROR
    "ERROR: TPL_${tplName}_LIBRARIES entry '${liblinkoption}' not a valid lib name link option!")
endfunction()


# @FUNCTION: tribits_extpkg_append_target_link_libraries_to_upstream_all_libs_targets_str()
#
# Append text calling `target_link_libraries(<prefix_libname> ... )` against
# the `<UpstreamPkg>::all_libs` targets for all of the direct enabled upstream
# dependencies listed in '<tplName>_LIB_ENABLED_DEPENDENCIES` (taking into
# account `PUBLIC` and `PRIVATE` dependencies).
#
function(tribits_extpkg_append_target_link_libraries_to_upstream_all_libs_targets_str
    tplName  prefix_libname  configFileStrInOut
  )
  set(configFileStr "${${configFileStrInOut}}")
  if (${tplName}_LIB_ENABLED_DEPENDENCIES)
    string(APPEND configFileStr
      "target_link_libraries(${prefix_libname}\n")
    foreach (upstreamTplDepEntry IN LISTS ${tplName}_LIB_ENABLED_DEPENDENCIES)
      tribits_extpkg_get_dep_name_and_vis(
        "${upstreamTplDepEntry}"  upstreamTplDepName  upstreamTplDepVis)
      if (upstreamTplDepVis STREQUAL "PUBLIC")
        string(APPEND configFileStr
          "  INTERFACE ${upstreamTplDepName}::all_libs  # i.e. PUBLIC\n")
      elseif(upstreamTplDepVis STREQUAL "PRIVATE")
        string(APPEND configFileStr
          "  INTERFACE $<LINK_ONLY:${upstreamTplDepName}::all_libs>  # i.e. PRIVATE\n")
      else()
        message(FATAL_ERROR "ERROR: Invalid visibility in entry '${upstreamTplDepEntry}'")
      endif()
    endforeach()
    string(APPEND configFileStr
      "  )\n")
  endif()
  set(${configFileStrInOut} "${configFileStr}" PARENT_SCOPE)
endfunction()
#
# NOTE: Above, the syntax for a private dependency is:
#
#   INTERFACE $<LINK_ONLY:upstreamLib>
#
# This has the effect of not bringing along the INTERFACE_INCLUDE_DIRECTORIES
# for upstreamLib so the include dirs for upstreamLib will not be listed on
# the compile lines of downstream object builds.  But it will result in the
# libraries being listed on link lines for downstsream library and exec links.


# @FUNCTION: tribits_extpkg_append_create_all_libs_target_str()
#
# Creates the ``<tplName>::all_libs`` target command text using input info and
# from ``TPL_<tplName>_INCLUDE_DIRS``.
#
# Usage::
#
#   tribits_extpkg_append_create_all_libs_target_str(
#     <tplName>
#     LIB_TARGETS_LIST <libTargetsList>
#     LIB_LINK_FLAGS_LIST <libLinkFlagsList>
#     CONFIG_FILE_STR_INOUT <configFileFragStrInOut>
#     )
#
# The arguments are:
#
#   ``<tplName>``: [in] Name of the external package/TPL
#
#   ``<libTargetsList>``: [in] List of targets created from processing
#   ``TPL_<tplName>_LIBRARIES``.
#
#   ``<libLinkFlagsList>``: [in] List of of ``-L<dir>`` library directory
#   paths entries found while processing ``TPL_<tplName>_LIBRARIES``.
#
#   ``<configFileFragStrInOut>``: [out] A string variable that will be
#   appended with the ``<tplName>::all_libs`` target statements.
#
function(tribits_extpkg_append_create_all_libs_target_str  tplName)

  # Parse commandline arguments

  cmake_parse_arguments(
     PARSE #prefix
     ""    #options
     "CONFIG_FILE_STR_INOUT"  #one_value_keywords
     "LIB_TARGETS_LIST;LIB_LINK_FLAGS_LIST"  #multi_value_keywords
     ${ARGN}
     )
  tribits_check_for_unparsed_arguments()

  # Set short-hand local vars
  set(libTargets "${PARSE_LIB_TARGETS_LIST}")
  set(libLinkFlags "${PARSE_LIB_LINK_FLAGS_LIST}")

  # Capture the initial input string in case the name of the var
  # 'configFileStr' is the same in the parent scope.
  set(configFileStrInit "${${PARSE_CONFIG_FILE_STR_INOUT}}")

  set(configFileStr "")

  # add_library()
  string(APPEND configFileStr
    "add_library(${tplName}::all_libs INTERFACE IMPORTED)\n")
  # target_link_libraries()
  if (libTargets)
    string(APPEND configFileStr
      "target_link_libraries(${tplName}::all_libs\n")
    foreach (libTarget IN LISTS libTargets)
      string(APPEND configFileStr
        "  INTERFACE ${libTarget}\n")
    endforeach()
    string(APPEND configFileStr
      "  )\n")
  endif()
  # target_include_directories()
  if (TPL_${tplName}_INCLUDE_DIRS)
    string(APPEND configFileStr
      "target_include_directories(${tplName}::all_libs SYSTEM\n")
    foreach (inclDir IN LISTS TPL_${tplName}_INCLUDE_DIRS)
      string(APPEND configFileStr
        "  INTERFACE \"${inclDir}\"\n")
    endforeach()
    string(APPEND configFileStr
      "  )\n")
  endif()
  # target_link_options()
  if (libLinkFlags)
    string(APPEND configFileStr
      "target_link_options(${tplName}::all_libs\n")
    foreach (likLinkFlag IN LISTS libLinkFlags)
      string(APPEND configFileStr
        "  INTERFACE \"${likLinkFlag}\"\n")
    endforeach()
    string(APPEND configFileStr
      "  )\n")
  endif()
  # Add trailing newline
  string(APPEND configFileStr
      "\n")

  # C) Set output arguments
  set(${PARSE_CONFIG_FILE_STR_INOUT} "${configFileStrInit}${configFileStr}"
    PARENT_SCOPE)

endfunction()
